package im.composer.audioservers.aggr;

import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.jaudiolibs.audioservers.AudioClient;
import org.jaudiolibs.audioservers.AudioConfiguration;
import org.jaudiolibs.audioservers.AudioServer;

/**
 * An AudioServer made up of serveral different audioservers, each must have the
 * same samplerate and buffersize.
 * 
 * @author David Zhang
 * 
 */
public class AggregateAudioServer implements AudioServer {

	private final float sampleRate;
	private final int bufferSize;
	private final AudioClient client;
	private final List<DelegatedClient> clients = new LinkedList<>();
	private List<FloatBuffer> inputs;
	private List<FloatBuffer> outputs;
	private long nanosecond_per_frame;
	private boolean active = false;

	public AggregateAudioServer(float sampleRate, int bufferSize, AudioClient client) {
		super();
		this.sampleRate = sampleRate;
		this.bufferSize = bufferSize;
		this.client = client;
		nanosecond_per_frame = (long) (bufferSize / sampleRate * Math.pow(10, 9));
	}

	public AudioClient newClient() {
		DelegatedClient c = new DelegatedClient(this);
		clients.add(c);
		return c;
	}

	@Override
	public void run() throws Exception {
		active = true;
		notifyChanges();
		runImpl();
	}

	private void runImpl() throws InterruptedException {
		while (active = client.process(System.nanoTime(), inputs, outputs, bufferSize)) {
			TimeUnit.NANOSECONDS.sleep(nanosecond_per_frame);
		}
	}

	void notifyChanges() {
		allocateBuffers(getAudioContext());
	}

	void exchangeData(DelegatedClient delegatedClient) {
		int n = 0;
		for (DelegatedClient dc : clients) {
			if (dc == null) {
				continue;
			}
			if (dc.equals(delegatedClient)) {
				break;
			}
			AudioConfiguration conf = dc.getAudioContext();
			if (conf == null) {
				continue;
			}
			n += conf.getInputChannelCount();
		}
		List<FloatBuffer> inputs = delegatedClient.inputs;
		for (int i = 0; i < inputs.size(); i++) {
			float[] arr = inputs.get(i).array();
			this.inputs.set(n + i, FloatBuffer.wrap(arr));
		}
		List<FloatBuffer> outputs = delegatedClient.outputs;
		for (int i = 0; i < outputs.size(); i++) {
			float[] arr = this.outputs.get(n + i).array();
			outputs.set(i, FloatBuffer.wrap(arr));
		}
	}

	private void allocateBuffers(AudioConfiguration context) {
		inputs = new ArrayList<>(context.getInputChannelCount());
		for (int i = 0; i < context.getInputChannelCount(); i++) {
			inputs.add(FloatBuffer.allocate(bufferSize));
		}
		outputs = new ArrayList<>(context.getOutputChannelCount());
		for (int i = 0; i < context.getOutputChannelCount(); i++) {
			outputs.add(FloatBuffer.allocate(bufferSize));
		}
	}

	@Override
	public AudioConfiguration getAudioContext() {
		int inputs = 0, outputs = 0;
		for (DelegatedClient dc : clients) {
			if (dc == null) {
				continue;
			}
			AudioConfiguration conf = dc.getAudioContext();
			if (conf == null) {
				continue;
			}
			inputs += conf.getInputChannelCount();
			outputs += conf.getOutputChannelCount();
		}
		return new AudioConfiguration(sampleRate, inputs, outputs, bufferSize, true);
	}

	@Override
	public boolean isActive() {
		return active;
	}

	@Override
	public void shutdown() {
		active = false;
	}

}
